/*-------------------------------------------------------------------------
 *
 * like.c
 *	  like 表达式处理代码。
 *
 *	 备注
 *		这是regexp.c代码的一个大黑客！由
 *		Keith Parks <emkxp01@mtcc.demon.co.uk>贡献（95年7月）。
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 * IDENTIFICATION
 *	src/backend/utils/adt/like.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <ctype.h>

#include "catalog/pg_collation.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "utils/builtins.h"
#include "utils/pg_locale.h"


#define LIKE_TRUE						1
#define LIKE_FALSE						0
#define LIKE_ABORT						(-1)


static int	fc_SB_MatchText(const char *fc_t, int fc_tlen, const char *fc_p, int fc_plen,
						 pg_locale_t locale, bool locale_is_c);
static text *SB_do_like_escape(text *, text *);

static int	fc_MB_MatchText(const char *fc_t, int fc_tlen, const char *fc_p, int fc_plen,
						 pg_locale_t locale, bool locale_is_c);
static text *MB_do_like_escape(text *, text *);

static int	fc_UTF8_MatchText(const char *fc_t, int fc_tlen, const char *fc_p, int fc_plen,
						   pg_locale_t locale, bool locale_is_c);

static int	fc_SB_IMatchText(const char *fc_t, int fc_tlen, const char *fc_p, int fc_plen,
						  pg_locale_t locale, bool locale_is_c);

static int	fc_GenericMatchText(const char *fc_s, int fc_slen, const char *fc_p, int fc_plen, Oid fc_collation);
static int	fc_Generic_Text_IC_like(text *fc_str, text *fc_pat, Oid fc_collation);

/*--------------------
 * MatchText 的支持例程。将给定的多字节流作为宽字符进行比较。如果匹配，则返回 1，否则返回 0。
 *--------------------
 */
static inline int fc_wchareq(const char *fc_p1, const char *fc_p2)
{
	int			fc_p1_len;

	/* 优化：快速比较第一个字节。 */
	if (*fc_p1 != *fc_p2)
		return 0;

	fc_p1_len = pg_mblen(fc_p1);
	if (pg_mblen(fc_p2) != fc_p1_len)
		return 0;

	/* 它们是相同长度的 */
	while (fc_p1_len--)
	{
		if (*fc_p1++ != *fc_p2++)
			return 0;
	}
	return 1;
}


/*
 * 以前我们在这里有一个例程 iwchareq()，试图对多字节字符进行不区分大小写的比较。
 * 然而，它完全没有起作用，因为它依赖于 tolower()，而 tolower() 只有一个单字节的 API...
 * 而 towlower() 也不会好到哪里去，因为我们没有合适的简单方法
 * 将一个单字符转换为系统的 wchar_t 格式。
 * 所以现在，我们只是使用 lower() 将字符串转为小写，并应用常规的 LIKE
 * 比较。在我们安装更好的区域设置支持时，应重新审视这个问题。
 */

/*
 * 不过，我们确实通过即时处理实现了对单字节编码的不区分大小写匹配。
 */
static char fc_SB_lower_char(unsigned char fc_c, pg_locale_t locale, bool locale_is_c)
{
	if (locale_is_c)
		return pg_ascii_tolower(fc_c);
#ifdef HAVE_LOCALE_T
	else if (locale)
		return tolower_l(fc_c, locale->info.lt);
#endif
	else
		return pg_tolower(fc_c);
}


#define NextByte(p, plen)	((p)++, (plen)--)

/* 设置为编译 like_match.c 以处理多字节字符 */
#define CHAREQ(p1, p2) fc_wchareq((p1), (p2))
#define NextChar(p, plen) \
	do { int fc___l = pg_mblen(p); (p) +=fc___l; (plen) -=fc___l; } while (0)
#define CopyAdvChar(dst, src, srclen) \
	do { int __l = pg_mblen(src); \
		 (srclen) -= __l; \
		 while (__l-- > 0) \
			 *(dst)++ = *(src)++; \
	   } while (0)

#define MatchText	fc_MB_MatchText
#define do_like_escape	MB_do_like_escape

#include "like_match.c"

/* 设置为编译 like_match.c 以处理单字节字符 */
#define CHAREQ(p1, p2) (*(p1) == *(p2))
#define NextChar(p, plen) NextByte((p), (plen))
#define CopyAdvChar(dst, src, srclen) (*(dst)++ = *(src)++, (srclen)--)

#define MatchText	fc_SB_MatchText
#define do_like_escape	SB_do_like_escape

#include "like_match.c"

/* 设置为编译 like_match.c 以进行单字节不区分大小写的匹配 */
#define MATCH_LOWER(t) fc_SB_lower_char((unsigned char) (t), fc_locale, fc_locale_is_c)
#define NextChar(p, plen) NextByte((p), (plen))
#define MatchText fc_SB_IMatchText

#include "like_match.c"

/* 设置以 UTF8 编码编译 like_match.c，使用快速 NextChar */

#define NextChar(p, plen) \
	do { (p)++; (plen)--; } while ((plen) > 0 && (*(p) & 0xC0) == 0x80 )
#define MatchText	fc_UTF8_MatchText

#include "like_match.c"

/* 所有不需要内联大小写折叠的情况的通用部分 */
static inline int fc_GenericMatchText(const char *fc_s, int fc_slen, const char *fc_p, int fc_plen, Oid fc_collation)
{
	if (fc_collation && !lc_ctype_is_c(fc_collation))
	{
		pg_locale_t locale = pg_newlocale_from_collation(fc_collation);

		if (locale && !locale->deterministic)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("nondeterministic collations are not supported for LIKE")));
	}

	if (pg_database_encoding_max_length() == 1)
		return fc_SB_MatchText(fc_s, fc_slen, fc_p, fc_plen, 0, true);
	else if (GetDatabaseEncoding() == PG_UTF8)
		return fc_UTF8_MatchText(fc_s, fc_slen, fc_p, fc_plen, 0, true);
	else
		return fc_MB_MatchText(fc_s, fc_slen, fc_p, fc_plen, 0, true);
}

static inline int fc_Generic_Text_IC_like(text *fc_str, text *fc_pat, Oid fc_collation)
{
	char	   *fc_s,
			   *fc_p;
	int			fc_slen,
				fc_plen;
	pg_locale_t locale = 0;
	bool		locale_is_c = false;

	if (!OidIsValid(fc_collation))
	{
		/*
		 * 这通常意味着解析器无法解决隐式排序的冲突，
		 * 因此以这种方式报告。
		 */
		ereport(ERROR,
				(errcode(ERRCODE_INDETERMINATE_COLLATION),
				 errmsg("could not determine which collation to use for ILIKE"),
				 errhint("Use the COLLATE clause to set the collation explicitly.")));
	}

	if (lc_ctype_is_c(fc_collation))
		locale_is_c = true;
	else
		locale = pg_newlocale_from_collation(fc_collation);

	if (locale && !locale->deterministic)
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("nondeterministic collations are not supported for ILIKE")));

	/* 
	 * 出于效率原因，在单字节情况下，我们不对模式和文本调用 lower()
	 *，而是对每个字符调用 SB_lower_char。在多字节情况下，我们没有太多选择 :-(。此外，
	 * ICU 不支持单字符大小写折叠，因此我们不得不走较长的路。
	 */

	if (pg_database_encoding_max_length() > 1 || (locale && locale->provider == COLLPROVIDER_ICU))
	{
		fc_pat = DatumGetTextPP(DirectFunctionCall1Coll(lower, fc_collation,
													 PointerGetDatum(fc_pat)));
		fc_p = VARDATA_ANY(fc_pat);
		fc_plen = VARSIZE_ANY_EXHDR(fc_pat);
		fc_str = DatumGetTextPP(DirectFunctionCall1Coll(lower, fc_collation,
													 PointerGetDatum(fc_str)));
		fc_s = VARDATA_ANY(fc_str);
		fc_slen = VARSIZE_ANY_EXHDR(fc_str);
		if (GetDatabaseEncoding() == PG_UTF8)
			return fc_UTF8_MatchText(fc_s, fc_slen, fc_p, fc_plen, 0, true);
		else
			return fc_MB_MatchText(fc_s, fc_slen, fc_p, fc_plen, 0, true);
	}
	else
	{
		fc_p = VARDATA_ANY(fc_pat);
		fc_plen = VARSIZE_ANY_EXHDR(fc_pat);
		fc_s = VARDATA_ANY(fc_str);
		fc_slen = VARSIZE_ANY_EXHDR(fc_str);
		return fc_SB_IMatchText(fc_s, fc_slen, fc_p, fc_plen, locale, locale_is_c);
	}
}

/* 
 *	接口例程由函数管理器调用
 */

Datum namelike(PG_FUNCTION_ARGS)
{
	Name		fc_str = PG_GETARG_NAME(0);
	text	   *fc_pat = PG_GETARG_TEXT_PP(1);
	bool		fc_result;
	char	   *fc_s,
			   *fc_p;
	int			fc_slen,
				fc_plen;

	fc_s = NameStr(*fc_str);
	fc_slen = strlen(fc_s);
	fc_p = VARDATA_ANY(fc_pat);
	fc_plen = VARSIZE_ANY_EXHDR(fc_pat);

	fc_result = (fc_GenericMatchText(fc_s, fc_slen, fc_p, fc_plen, PG_GET_COLLATION()) == LIKE_TRUE);

	PG_RETURN_BOOL(fc_result);
}

Datum namenlike(PG_FUNCTION_ARGS)
{
	Name		fc_str = PG_GETARG_NAME(0);
	text	   *fc_pat = PG_GETARG_TEXT_PP(1);
	bool		fc_result;
	char	   *fc_s,
			   *fc_p;
	int			fc_slen,
				fc_plen;

	fc_s = NameStr(*fc_str);
	fc_slen = strlen(fc_s);
	fc_p = VARDATA_ANY(fc_pat);
	fc_plen = VARSIZE_ANY_EXHDR(fc_pat);

	fc_result = (fc_GenericMatchText(fc_s, fc_slen, fc_p, fc_plen, PG_GET_COLLATION()) != LIKE_TRUE);

	PG_RETURN_BOOL(fc_result);
}

Datum textlike(PG_FUNCTION_ARGS)
{
	text	   *fc_str = PG_GETARG_TEXT_PP(0);
	text	   *fc_pat = PG_GETARG_TEXT_PP(1);
	bool		fc_result;
	char	   *fc_s,
			   *fc_p;
	int			fc_slen,
				fc_plen;

	fc_s = VARDATA_ANY(fc_str);
	fc_slen = VARSIZE_ANY_EXHDR(fc_str);
	fc_p = VARDATA_ANY(fc_pat);
	fc_plen = VARSIZE_ANY_EXHDR(fc_pat);

	fc_result = (fc_GenericMatchText(fc_s, fc_slen, fc_p, fc_plen, PG_GET_COLLATION()) == LIKE_TRUE);

	PG_RETURN_BOOL(fc_result);
}

Datum textnlike(PG_FUNCTION_ARGS)
{
	text	   *fc_str = PG_GETARG_TEXT_PP(0);
	text	   *fc_pat = PG_GETARG_TEXT_PP(1);
	bool		fc_result;
	char	   *fc_s,
			   *fc_p;
	int			fc_slen,
				fc_plen;

	fc_s = VARDATA_ANY(fc_str);
	fc_slen = VARSIZE_ANY_EXHDR(fc_str);
	fc_p = VARDATA_ANY(fc_pat);
	fc_plen = VARSIZE_ANY_EXHDR(fc_pat);

	fc_result = (fc_GenericMatchText(fc_s, fc_slen, fc_p, fc_plen, PG_GET_COLLATION()) != LIKE_TRUE);

	PG_RETURN_BOOL(fc_result);
}

Datum bytealike(PG_FUNCTION_ARGS)
{
	bytea	   *fc_str = PG_GETARG_BYTEA_PP(0);
	bytea	   *fc_pat = PG_GETARG_BYTEA_PP(1);
	bool		fc_result;
	char	   *fc_s,
			   *fc_p;
	int			fc_slen,
				fc_plen;

	fc_s = VARDATA_ANY(fc_str);
	fc_slen = VARSIZE_ANY_EXHDR(fc_str);
	fc_p = VARDATA_ANY(fc_pat);
	fc_plen = VARSIZE_ANY_EXHDR(fc_pat);

	fc_result = (fc_SB_MatchText(fc_s, fc_slen, fc_p, fc_plen, 0, true) == LIKE_TRUE);

	PG_RETURN_BOOL(fc_result);
}

Datum byteanlike(PG_FUNCTION_ARGS)
{
	bytea	   *fc_str = PG_GETARG_BYTEA_PP(0);
	bytea	   *fc_pat = PG_GETARG_BYTEA_PP(1);
	bool		fc_result;
	char	   *fc_s,
			   *fc_p;
	int			fc_slen,
				fc_plen;

	fc_s = VARDATA_ANY(fc_str);
	fc_slen = VARSIZE_ANY_EXHDR(fc_str);
	fc_p = VARDATA_ANY(fc_pat);
	fc_plen = VARSIZE_ANY_EXHDR(fc_pat);

	fc_result = (fc_SB_MatchText(fc_s, fc_slen, fc_p, fc_plen, 0, true) != LIKE_TRUE);

	PG_RETURN_BOOL(fc_result);
}

/* 
 * 不区分大小写的版本
 */

Datum nameiclike(PG_FUNCTION_ARGS)
{
	Name		fc_str = PG_GETARG_NAME(0);
	text	   *fc_pat = PG_GETARG_TEXT_PP(1);
	bool		fc_result;
	text	   *fc_strtext;

	fc_strtext = DatumGetTextPP(DirectFunctionCall1(name_text,
												 NameGetDatum(fc_str)));
	fc_result = (fc_Generic_Text_IC_like(fc_strtext, fc_pat, PG_GET_COLLATION()) == LIKE_TRUE);

	PG_RETURN_BOOL(fc_result);
}

Datum nameicnlike(PG_FUNCTION_ARGS)
{
	Name		fc_str = PG_GETARG_NAME(0);
	text	   *fc_pat = PG_GETARG_TEXT_PP(1);
	bool		fc_result;
	text	   *fc_strtext;

	fc_strtext = DatumGetTextPP(DirectFunctionCall1(name_text,
												 NameGetDatum(fc_str)));
	fc_result = (fc_Generic_Text_IC_like(fc_strtext, fc_pat, PG_GET_COLLATION()) != LIKE_TRUE);

	PG_RETURN_BOOL(fc_result);
}

Datum texticlike(PG_FUNCTION_ARGS)
{
	text	   *fc_str = PG_GETARG_TEXT_PP(0);
	text	   *fc_pat = PG_GETARG_TEXT_PP(1);
	bool		fc_result;

	fc_result = (fc_Generic_Text_IC_like(fc_str, fc_pat, PG_GET_COLLATION()) == LIKE_TRUE);

	PG_RETURN_BOOL(fc_result);
}

Datum texticnlike(PG_FUNCTION_ARGS)
{
	text	   *fc_str = PG_GETARG_TEXT_PP(0);
	text	   *fc_pat = PG_GETARG_TEXT_PP(1);
	bool		fc_result;

	fc_result = (fc_Generic_Text_IC_like(fc_str, fc_pat, PG_GET_COLLATION()) != LIKE_TRUE);

	PG_RETURN_BOOL(fc_result);
}

/* 
 * like_escape() --- 给定一个模式和一个 ESCAPE 字符串，
 * 将模式转换为使用 Postgres 的标准反斜杠转义约定。
 */
Datum like_escape(PG_FUNCTION_ARGS)
{
	text	   *fc_pat = PG_GETARG_TEXT_PP(0);
	text	   *fc_esc = PG_GETARG_TEXT_PP(1);
	text	   *fc_result;

	if (pg_database_encoding_max_length() == 1)
		fc_result = SB_do_like_escape(fc_pat, fc_esc);
	else
		fc_result = MB_do_like_escape(fc_pat, fc_esc);

	PG_RETURN_TEXT_P(fc_result);
}

/* 
 * like_escape_bytea() --- 给定一个模式和一个 ESCAPE 字符串，
 * 将模式转换为使用 Postgres 的标准反斜杠转义约定。
 */
Datum like_escape_bytea(PG_FUNCTION_ARGS)
{
	bytea	   *fc_pat = PG_GETARG_BYTEA_PP(0);
	bytea	   *fc_esc = PG_GETARG_BYTEA_PP(1);
	bytea	   *fc_result = SB_do_like_escape((text *) fc_pat, (text *) fc_esc);

	PG_RETURN_BYTEA_P((bytea *) fc_result);
}
